from ui.components.base.widget import TgBaseWidget
import pyqtgraph as pg
from PySide6.QtWidgets import QVBoxLayout
from PySide6.QtCore import QDateTime, Qt
import numpy as np
from datetime import datetime, date
import pandas as pd

class TgPlotWidget(TgBaseWidget):
    def __init__(self, title="", parent=None, time_axis=False):
        """
        创建图表组件
        
        Args:
            title: 图表标题
            parent: 父组件
            time_axis: 是否使用时间轴（X轴），如果为True则自动处理时间数据转换
        """
        super().__init__(parent)
        
        # 根据是否为时间轴创建相应的坐标轴
        axis_items = None
        if time_axis:
            axis_items = {'bottom': pg.DateAxisItem()}
        
        self.plot_widget = pg.PlotWidget(axisItems=axis_items)
        self.plot_widget.setTitle(title)
        self.time_axis = time_axis  # 记录是否为时间轴模式
        
        # 添加右y轴支持
        self.right_axis = None
        self.right_plots = []  # 存储右y轴的绘图项
        
        # 设置专业的白色网格背景
        self.plot_widget.setBackground('#f8f9fa')  # 浅灰白色背景，更专业
        
        # 显示网格
        self.plot_widget.showGrid(x=True, y=True, alpha=0.3)
        
        # 确保坐标轴显示
        self.plot_widget.showAxis('bottom', True)
        self.plot_widget.showAxis('left', True)
        
        # 默认禁用缩放和平移，但可以通过方法启用
        self.set_mouse_interaction(enabled=False)
        
        # 禁用自动范围调整的交互
        self.plot_widget.getViewBox().setMenuEnabled(False)
        
        # 禁用所有可能的交互行为
        view_box = self.plot_widget.getViewBox()
        view_box.setMouseMode(view_box.RectMode)  # 设置为矩形模式但不启用
        
        # 添加十字线用于显示数值
        self.crosshair_v = pg.InfiniteLine(angle=90, movable=False, pen=pg.mkPen(color='gray', width=1, style=Qt.PenStyle.DashLine))
        self.crosshair_h = pg.InfiniteLine(angle=0, movable=False, pen=pg.mkPen(color='gray', width=1, style=Qt.PenStyle.DashLine))
        self.plot_widget.addItem(self.crosshair_v, ignoreBounds=True)
        self.plot_widget.addItem(self.crosshair_h, ignoreBounds=True)
        
        # 添加文本标签显示数值
        self.value_label = pg.TextItem(anchor=(0, 1), color='black', fill=pg.mkBrush(255, 255, 255, 200))
        self.plot_widget.addItem(self.value_label)
        
        # 连接鼠标移动事件
        self.plot_widget.scene().sigMouseMoved.connect(self.mouse_moved)
        
        # 存储绘制的曲线数据用于查找最近值
        self.plot_curves = []
        self.right_plot_curves = []  # 存储右y轴曲线数据
        
        self.plot_widget.addLegend()

        layout = QVBoxLayout()
        layout.addWidget(self.plot_widget)
        self.setLayout(layout)

    def mouse_moved(self, evt):
        """处理鼠标移动事件，显示十字线和数值"""
        if self.plot_widget.plotItem.vb.sceneBoundingRect().contains(evt):
            mouse_point = self.plot_widget.plotItem.vb.mapSceneToView(evt)
            
            # 更新十字线位置
            self.crosshair_v.setPos(mouse_point.x())
            self.crosshair_h.setPos(mouse_point.y())
            
            # 查找最近的数据点并显示值
            if self.plot_curves or self.right_plot_curves:
                closest_curve = None
                closest_distance = float('inf')
                closest_x = None
                closest_y = None
                closest_name = ""
                
                # 检查左y轴曲线
                for curve_data in self.plot_curves:
                    x_data, y_data, name = curve_data['x'], curve_data['y'], curve_data['name']
                    
                    # 确保数据是数值类型的数组
                    try:
                        x_array = np.array(x_data, dtype=float)
                        y_array = np.array(y_data, dtype=float)
                    except (ValueError, TypeError):
                        continue  # 跳过无法转换的数据
                    
                    if len(x_array) == 0 or len(y_array) == 0:
                        continue
                    
                    # 找到x轴上最接近的点
                    distances = np.abs(x_array - mouse_point.x())
                    min_idx = np.argmin(distances)
                    
                    # 计算总距离（考虑x和y的差异）
                    if min_idx < len(y_array):
                        x_val = x_array[min_idx]
                        y_val = y_array[min_idx]
                        
                        # 归一化距离计算
                        x_range = np.max(x_array) - np.min(x_array)
                        y_range = np.max(y_array) - np.min(y_array)
                        
                        # 放宽条件，允许较小的范围
                        if x_range > 0:
                            normalized_x_dist = abs(x_val - mouse_point.x()) / x_range
                            # 如果y_range很小，就主要依赖x轴距离
                            if y_range > 0:
                                normalized_y_dist = abs(y_val - mouse_point.y()) / y_range
                                total_distance = normalized_x_dist + normalized_y_dist * 0.1
                            else:
                                total_distance = normalized_x_dist
                            
                            if total_distance < closest_distance:
                                closest_distance = total_distance
                                closest_x = x_val
                                closest_y = y_val
                                closest_name = name
                
                # 检查右y轴曲线
                for curve_data in self.right_plot_curves:
                    x_data, y_data, name = curve_data['x'], curve_data['y'], curve_data['name']
                    
                    # 确保数据是数值类型的数组
                    try:
                        x_array = np.array(x_data, dtype=float)
                        y_array = np.array(y_data, dtype=float)
                    except (ValueError, TypeError):
                        continue
                    
                    if len(x_array) == 0 or len(y_array) == 0:
                        continue
                    
                    # 对于右y轴，需要将y值转换到左y轴坐标系进行距离计算
                    if self.right_axis:
                        # 获取右y轴的范围
                        right_range = self.right_axis.range
                        left_range = self.plot_widget.getViewBox().viewRange()[1]
                        
                        # 将右y轴的y值映射到左y轴坐标系
                        if right_range[1] != right_range[0]:
                            normalized_y = (y_array - right_range[0]) / (right_range[1] - right_range[0])
                            mapped_y = left_range[0] + normalized_y * (left_range[1] - left_range[0])
                        else:
                            mapped_y = y_array
                    else:
                        mapped_y = y_array
                    
                    # 找到x轴上最接近的点
                    distances = np.abs(x_array - mouse_point.x())
                    min_idx = np.argmin(distances)
                    
                    # 计算总距离
                    if min_idx < len(y_array):
                        x_val = x_array[min_idx]
                        y_val = y_array[min_idx]  # 原始右y轴值
                        mapped_y_val = mapped_y[min_idx] if hasattr(mapped_y, '__getitem__') else mapped_y
                        
                        # 归一化距离计算
                        x_range = np.max(x_array) - np.min(x_array)
                        y_range = np.max(mapped_y) - np.min(mapped_y) if hasattr(mapped_y, '__iter__') else 0
                        
                        if x_range > 0:
                            normalized_x_dist = abs(x_val - mouse_point.x()) / x_range
                            if y_range > 0:
                                normalized_y_dist = abs(mapped_y_val - mouse_point.y()) / y_range
                                total_distance = normalized_x_dist + normalized_y_dist * 0.1
                            else:
                                total_distance = normalized_x_dist
                            
                            if total_distance < closest_distance:
                                closest_distance = total_distance
                                closest_x = x_val
                                closest_y = y_val  # 使用原始右y轴值
                                closest_name = f"{name} (右)"  # 标记为右y轴
                
                # 显示最近点的信息
                if closest_x is not None:
                    if self.time_axis:
                        # 时间轴模式下，将时间戳转换为可读格式
                        time_str = datetime.fromtimestamp(closest_x).strftime('%Y-%m-%d')
                        # 检查数值范围，如果是小数且小于100，可能是百分比数据
                        if isinstance(closest_y, (int, float)) and 0 <= closest_y <= 100 and closest_y != int(closest_y):
                            value_text = f"{closest_name}\n时间: {time_str}\n数值: {closest_y:.2f}%"
                        else:
                            value_text = f"{closest_name}\n时间: {time_str}\n数值: {closest_y:,.0f}"
                    else:
                        value_text = f"{closest_name}\nX: {closest_x:.2f}\nY: {closest_y:.2f}"
                    
                    self.value_label.setText(value_text)
                    
                    # 获取视图范围和视图框几何信息
                    view_range = self.plot_widget.getViewBox().viewRange()
                    x_range = view_range[0]
                    y_range = view_range[1]
                    
                    # 获取视图框的像素尺寸，更精确地计算偏移量
                    viewbox_rect = self.plot_widget.getViewBox().sceneBoundingRect()
                    if viewbox_rect.width() > 0 and viewbox_rect.height() > 0:
                        # 计算数据坐标到像素的转换比例
                        x_span = x_range[1] - x_range[0]
                        y_span = y_range[1] - y_range[0]
                        
                        # 设置固定的像素偏移量
                        pixel_offset_x = 15  # 减小偏移量，更紧跟鼠标
                        pixel_offset_y = 15  # 减小偏移量，更紧跟鼠标
                        
                        # 精确转换为数据坐标偏移量
                        data_offset_x = x_span * (pixel_offset_x / viewbox_rect.width())
                        data_offset_y = y_span * (pixel_offset_y / viewbox_rect.height())
                        
                        # 计算Tooltip预估尺寸（基于文本长度）
                        text_lines = value_text.split('\n')
                        max_line_length = max(len(line) for line in text_lines) if text_lines else 10
                        tooltip_width_pixels = max_line_length * 8  # 估算字符宽度
                        tooltip_height_pixels = len(text_lines) * 16 + 10  # 估算行高加边距
                        
                        # 转换Tooltip尺寸为数据坐标
                        tooltip_width_data = x_span * (tooltip_width_pixels / viewbox_rect.width())
                        tooltip_height_data = y_span * (tooltip_height_pixels / viewbox_rect.height())
                        
                        # 默认位置：鼠标右上方
                        label_x = mouse_point.x() + data_offset_x
                        label_y = mouse_point.y() + data_offset_y
                        anchor = (0, 0)  # 左下角锚点
                        
                        # 边界检查：考虑Tooltip实际尺寸，减小边距
                        margin_x = x_span * 0.01  # 减小边距至1%
                        margin_y = y_span * 0.01  # 减小边距至1%
                        
                        # 检查右边界（考虑Tooltip宽度）
                        if label_x + tooltip_width_data > x_range[1] - margin_x:
                            label_x = mouse_point.x() - data_offset_x
                            anchor = (1, anchor[1])  # 右锚点
                        
                        # 检查左边界
                        if label_x < x_range[0] + margin_x:
                            label_x = mouse_point.x() + data_offset_x
                            anchor = (0, anchor[1])  # 左锚点
                        
                        # 检查上边界（考虑Tooltip高度）
                        # 使用下锚点时，Tooltip从锚点向上延伸，需要检查 label_y + tooltip_height_data
                        if label_y + tooltip_height_data > y_range[1] - margin_y:
                            label_y = mouse_point.y() - data_offset_y
                            anchor = (anchor[0], 1)  # 切换到上锚点，Tooltip向下延伸
                        
                        # 检查下边界
                        # 使用上锚点时，Tooltip从锚点向下延伸，需要检查 label_y - tooltip_height_data  
                        if anchor[1] == 1 and label_y - tooltip_height_data < y_range[0] + margin_y:
                            label_y = mouse_point.y() + data_offset_y
                            anchor = (anchor[0], 0)  # 切换回下锚点
                        elif anchor[1] == 0 and label_y < y_range[0] + margin_y:
                            label_y = mouse_point.y() + data_offset_y
                            anchor = (anchor[0], 0)  # 保持下锚点
                        
                        # 最终边界限制：确保Tooltip完全在视图范围内
                        if anchor[0] == 0:  # 左锚点，Tooltip向右延伸
                            label_x = max(label_x, x_range[0] + margin_x)
                            label_x = min(label_x, x_range[1] - tooltip_width_data - margin_x)
                        else:  # 右锚点，Tooltip向左延伸
                            label_x = max(label_x, x_range[0] + tooltip_width_data + margin_x)
                            label_x = min(label_x, x_range[1] - margin_x)
                            
                        if anchor[1] == 0:  # 下锚点，Tooltip向上延伸
                            label_y = max(label_y, y_range[0] + margin_y)
                            label_y = min(label_y, y_range[1] - tooltip_height_data - margin_y)
                        else:  # 上锚点，Tooltip向下延伸
                            label_y = max(label_y, y_range[0] + tooltip_height_data + margin_y)
                            label_y = min(label_y, y_range[1] - margin_y)
                    else:
                        # 备用方案：如果无法获取准确的视图框尺寸
                        x_span = x_range[1] - x_range[0]
                        y_span = y_range[1] - y_range[0]
                        
                        data_offset_x = x_span * 0.02
                        data_offset_y = y_span * 0.03
                        
                        label_x = mouse_point.x() + data_offset_x
                        label_y = mouse_point.y() + data_offset_y
                        anchor = (0, 0)
                    
                    self.value_label.setAnchor(anchor)
                    self.value_label.setPos(label_x, label_y)
                else:
                    self.value_label.setText("")

    def _convert_to_timestamp(self, x):
        """将时间相关的数据转换为时间戳
        
        仅在时间轴模式下调用此方法
        
        Args:
            x: 可以是日期时间对象、pandas Series、numpy array 或数值列表
        
        Returns:
            转换后的时间戳数组或原始数据（如果不是时间类型）
        """
        if isinstance(x, (pd.Series, pd.Index)):
            # 处理 pandas Series 或 Index
            def get_timestamp(item):
                if hasattr(item, 'timestamp'):
                    return item.timestamp()
                elif isinstance(item, date):
                    # 对于 date 对象，转换为 datetime 再获取时间戳
                    return datetime.combine(item, datetime.min.time()).timestamp()
                else:
                    return item
            return x.map(get_timestamp)
        elif isinstance(x, (list, tuple, np.ndarray)):
            # 处理列表、元组或 numpy 数组
            converted = []
            for item in x:
                if hasattr(item, 'timestamp'):
                    converted.append(item.timestamp())
                elif isinstance(item, date):
                    converted.append(datetime.combine(item, datetime.min.time()).timestamp())
                else:
                    converted.append(item)
            return converted
        else:
            # 单个值或其他类型，直接返回
            return x

    def clear(self):
        """清空图表内容"""
        # 先清理图例，避免已删除对象的引用错误
        if hasattr(self.plot_widget.plotItem, 'legend') and self.plot_widget.plotItem.legend is not None:
            # 清空图例中的所有项
            legend = self.plot_widget.plotItem.legend
            legend.clear()
        
        self.plot_widget.clear()
        self.plot_curves.clear()  # 清空曲线数据
        self.right_plot_curves.clear()  # 清空右y轴曲线数据
        
        # 清空右y轴相关项
        if self.right_axis:
            # 断开信号连接，避免内存泄漏
            if hasattr(self, 'right_viewbox'):
                try:
                    self.plot_widget.plotItem.vb.sigResized.disconnect(self._update_right_viewbox)
                except:
                    pass  # 可能已经断开连接
                
                # 先清理右ViewBox中的所有绘图项
                if self.right_viewbox is not None:
                    for plot_item in self.right_plots:
                        try:
                            self.right_viewbox.removeItem(plot_item)
                        except:
                            pass  # 可能已经移除
                
                # 从场景中移除右ViewBox
                if self.right_viewbox and self.right_viewbox.scene():
                    self.plot_widget.scene().removeItem(self.right_viewbox)
                
                # 从布局中移除右y轴
                try:
                    self.plot_widget.plotItem.layout.removeItem(self.right_axis)
                except:
                    pass  # 可能已经移除
                
                # 清理右ViewBox引用
                self.right_viewbox = None
            
            self.right_axis = None
        self.right_plots.clear()
        
        # 确保主ViewBox的Z值正确，网格线可见
        main_viewbox = self.plot_widget.plotItem.vb
        main_viewbox.setZValue(0)  # 重置为默认值
        
        # 重新设置网格显示
        self.plot_widget.showGrid(x=True, y=True, alpha=0.3)
        
        # 确保坐标轴显示
        self.plot_widget.showAxis('bottom', True)
        self.plot_widget.showAxis('left', True)
        
        self.plot_widget.addLegend() # Re-add legend after clearing
        
        # 重新添加十字线和标签
        self.plot_widget.addItem(self.crosshair_v, ignoreBounds=True)
        self.plot_widget.addItem(self.crosshair_h, ignoreBounds=True)
        self.plot_widget.addItem(self.value_label)

    def add_line_chart(self, x, y, name, color, style='solid', width=1):
        """添加线图
        
        Args:
            x: X轴数据，如果是时间轴模式则支持时间戳、日期时间对象、pandas Series等
            y: Y轴数据
            name: 图例名称
            color: 线条颜色
            style: 线条样式，支持 'solid'(实线), 'dash'(虚线), 'dot'(点线), 'dashdot'(点划线)
            width: 线条宽度
        """
        # 只在时间轴模式下才进行时间戳转换
        if self.time_axis:
            x_data = self._convert_to_timestamp(x)
        else:
            x_data = x
        
        # 设置线条样式
        pen_style = Qt.PenStyle.SolidLine  # 默认实线
        if style == 'dash':
            pen_style = Qt.PenStyle.DashLine
        elif style == 'dot':
            pen_style = Qt.PenStyle.DotLine
        elif style == 'dashdot':
            pen_style = Qt.PenStyle.DashDotLine
        elif style == 'solid':
            pen_style = Qt.PenStyle.SolidLine
            
        # 存储曲线数据用于鼠标悬停
        self.plot_curves.append({
            'x': x_data,
            'y': y,
            'name': name
        })
            
        # 创建带样式的笔刷
        pen = pg.mkPen(color=color, width=width, style=pen_style)
        self.plot_widget.plot(x_data, y, pen=pen, name=name)

    def add_line_chart_right_axis(self, x, y, name, color, axis_label="", style='solid', width=1):
        """添加右y轴线图
        
        Args:
            x: X轴数据，如果是时间轴模式则支持时间戳、日期时间对象、pandas Series等
            y: Y轴数据
            name: 图例名称
            color: 线条颜色
            axis_label: 右y轴标签
            style: 线条样式，支持 'solid'(实线), 'dash'(虚线), 'dot'(点线), 'dashdot'(点划线)
            width: 线条宽度
        """
        # 创建右y轴（如果还没有）
        if self.right_axis is None:
            self.right_axis = pg.AxisItem('right')
            self.right_axis.setLabel(axis_label)
            
            # 创建一个新的ViewBox用于右y轴
            self.right_viewbox = pg.ViewBox()
            
            # 将右y轴添加到布局中
            self.plot_widget.plotItem.layout.addItem(self.right_axis, 2, 3)
            
            # 将右ViewBox添加到场景中，但设置为完全透明，不绘制背景
            self.plot_widget.scene().addItem(self.right_viewbox)
            
            # 链接x轴，使右y轴的ViewBox与主ViewBox同步x轴范围
            self.right_viewbox.setXLink(self.plot_widget.plotItem.vb)
            
            # 连接右y轴到右ViewBox
            self.right_axis.linkToView(self.right_viewbox)
            
            # 关键修复：禁用右ViewBox的所有背景绘制和交互
            self.right_viewbox.setMouseEnabled(x=False, y=False)
            self.right_viewbox.setMenuEnabled(False)
            # 设置右ViewBox完全透明，不绘制任何背景
            self.right_viewbox.setBackgroundColor(None)
            # 设置Z值确保在主ViewBox之下
            self.right_viewbox.setZValue(-10)
            
            # 监听主ViewBox的尺寸变化，同步右ViewBox
            self.plot_widget.plotItem.vb.sigResized.connect(self._update_right_viewbox)
            
            # 初始化右ViewBox的几何形状
            self._update_right_viewbox()
        
        # 只在时间轴模式下才进行时间戳转换
        if self.time_axis:
            x_data = self._convert_to_timestamp(x)
        else:
            x_data = x
        
        # 设置线条样式
        pen_style = Qt.PenStyle.SolidLine  # 默认实线
        if style == 'dash':
            pen_style = Qt.PenStyle.DashLine
        elif style == 'dot':
            pen_style = Qt.PenStyle.DotLine
        elif style == 'dashdot':
            pen_style = Qt.PenStyle.DashDotLine
        elif style == 'solid':
            pen_style = Qt.PenStyle.SolidLine
            
        # 存储右y轴曲线数据用于鼠标悬停
        self.right_plot_curves.append({
            'x': x_data,
            'y': y,
            'name': name
        })
        
        # 在右ViewBox中创建绘图项
        pen = pg.mkPen(color=color, width=width, style=pen_style)
        curve = pg.PlotCurveItem(x_data, y, pen=pen)
        self.right_viewbox.addItem(curve)
        self.right_plots.append(curve)
        
        # 手动添加到图例（因为右y轴的项不会自动添加）
        if hasattr(self.plot_widget.plotItem, 'legend') and self.plot_widget.plotItem.legend is not None:
            # 检查图例中是否已经存在相同名称的项，避免重复添加
            try:
                legend = self.plot_widget.plotItem.legend
                existing_items = []
                if hasattr(legend, 'items'):
                    for item in legend.items:
                        if len(item) >= 2 and hasattr(item[1], 'text'):
                            existing_items.append(item[1].text)
                
                if name not in existing_items:
                    legend.addItem(curve, name)
            except (AttributeError, IndexError, RuntimeError):
                # 如果图例结构发生变化或出现运行时错误，尝试直接添加
                try:
                    self.plot_widget.plotItem.legend.addItem(curve, name)
                except:
                    pass  # 如果仍然失败，就跳过图例添加
    
    def _update_right_viewbox(self):
        """更新右ViewBox的几何形状以匹配主ViewBox"""
        if self.right_axis and hasattr(self, 'right_viewbox') and self.right_viewbox is not None:
            # 获取主ViewBox的几何形状
            main_geometry = self.plot_widget.plotItem.vb.sceneBoundingRect()
            
            # 设置右ViewBox的几何形状
            self.right_viewbox.setGeometry(main_geometry)
            
            # 确保右ViewBox完全透明，不会遮挡主ViewBox的任何元素
            self.right_viewbox.setZValue(-10)
            
            # 强制刷新右ViewBox的视图范围
            self.right_viewbox.updateViewRange()
            
            # 确保主ViewBox的网格线在最前面
            main_viewbox = self.plot_widget.plotItem.vb
            main_viewbox.setZValue(10)  # 确保主ViewBox在前面
            
            # 强制刷新主ViewBox以确保网格线正确显示
            main_viewbox.update()

    def auto_range(self):
        """自动调整视图范围到所有数据，并设置边界"""
        # 处理左y轴数据
        if self.plot_curves:
            # 计算所有曲线的数据范围
            all_x = []
            all_y = []
            
            for curve_data in self.plot_curves:
                x_data = curve_data['x']
                y_data = curve_data['y']
                
                if hasattr(x_data, '__iter__'):
                    all_x.extend(list(x_data))
                else:
                    all_x.append(x_data)
                    
                if hasattr(y_data, '__iter__'):
                    all_y.extend(list(y_data))
                else:
                    all_y.append(y_data)
            
            if all_x and all_y:
                x_min, x_max = min(all_x), max(all_x)
                y_min, y_max = min(all_y), max(all_y)
                
                # 添加边距
                x_range = x_max - x_min
                y_range = y_max - y_min
                
                # 添加边距
                x_padding = x_range * 0.05 if x_range > 0 else abs(x_max) * 0.1 if x_max != 0 else 1
                y_padding = y_range * 0.05 if y_range > 0 else abs(y_max) * 0.1 if y_max != 0 else 0.01
                
                x_lim_min = x_min - x_padding
                x_lim_max = x_max + x_padding
                y_lim_min = y_min - y_padding
                y_lim_max = y_max + y_padding
                
                # 设置视图范围，但不设置严格边界限制
                vb = self.plot_widget.getViewBox()
                # 先清除任何现有的限制
                vb.setLimits(xMin=None, xMax=None, yMin=None, yMax=None)
                
                # 设置视图范围
                vb.setRange(
                    xRange=[x_lim_min, x_lim_max],
                    yRange=[y_lim_min, y_lim_max],
                    padding=0
                )
                
                # 确保坐标轴可见
                self.plot_widget.showAxis('bottom', True)
                self.plot_widget.showAxis('left', True)
                
                # 强制更新视图
                vb.updateViewRange()
                self.plot_widget.update()
        else:
            # 如果没有曲线数据，使用默认的自动范围
            vb = self.plot_widget.getViewBox()
            vb.autoRange()
            
        # 处理右y轴数据
        if self.right_plot_curves and hasattr(self, 'right_viewbox') and self.right_viewbox is not None:
            all_right_y = []
            
            for curve_data in self.right_plot_curves:
                y_data = curve_data['y']
                
                if hasattr(y_data, '__iter__'):
                    all_right_y.extend(list(y_data))
                else:
                    all_right_y.append(y_data)
            
            if all_right_y:
                right_y_min, right_y_max = min(all_right_y), max(all_right_y)
                right_y_range = right_y_max - right_y_min
                right_y_padding = right_y_range * 0.05 if right_y_range > 0 else 1
                
                right_y_lim_min = right_y_min - right_y_padding
                right_y_lim_max = right_y_max + right_y_padding
                
                # 设置右ViewBox的y轴范围和边界
                self.right_viewbox.setLimits(yMin=right_y_lim_min, yMax=right_y_lim_max)
                self.right_viewbox.setYRange(
                    right_y_lim_min,
                    right_y_lim_max,
                    padding=0
                )
                
                # 延迟更新右ViewBox几何形状，确保不会遮挡网格线
                def delayed_grid_fix():
                    if hasattr(self, 'right_viewbox') and self.right_viewbox is not None:
                        # 确保主ViewBox的网格线在最前面
                        main_viewbox = self.plot_widget.plotItem.vb
                        main_viewbox.setZValue(10)
                        # 强制刷新主ViewBox以确保网格线正确显示
                        main_viewbox.update()
                        self._update_right_viewbox()
                
                from PySide6.QtCore import QTimer
                QTimer.singleShot(10, delayed_grid_fix)

    def set_mouse_interaction(self, enabled=True):
        """启用或禁用鼠标交互（缩放、平移）
        
        Args:
            enabled: True表示启用，False表示禁用
        """
        self.plot_widget.setMouseEnabled(x=enabled, y=enabled)

    def add_bar_chart(self, x, y, width=None, colors=None):
        """添加柱状图
        
        Args:
            x: X轴数据，如果是时间轴模式则支持时间戳、日期时间对象、pandas Series等
            y: Y轴数据
            width: 柱子宽度
            colors: 柱子颜色列表
        """
        # 只在时间轴模式下才进行时间戳转换
        if self.time_axis:
            x_data = self._convert_to_timestamp(x)
        else:
            x_data = x
        
        if colors is None:
            colors = ['b'] * len(x_data) # Default to blue if no colors provided
        
        # Convert colors to QColor objects if they are not already
        brushes = [pg.mkBrush(color) for color in colors]

        if width is None:
            # Calculate width based on the minimum difference between x values
            if len(x_data) > 1:
                x_array = np.array(x_data)
                width = np.min(np.diff(x_array)) * 0.8 # 80% of the minimum interval
            else:
                width = 1 # Default width if only one bar

        bar_graph = pg.BarGraphItem(x=x_data, height=y, width=width, brushes=brushes)
        self.plot_widget.addItem(bar_graph)

    def set_time_axis(self, enabled=True):
        """动态设置是否启用时间轴模式
        
        Args:
            enabled: 是否启用时间轴模式
        """
        self.time_axis = enabled
        if enabled:
            # 如果启用时间轴，需要重新创建 PlotWidget
            # 因为 axisItems 只能在创建时设置
            old_widget = self.plot_widget
            self.plot_widget = pg.PlotWidget(axisItems={'bottom': pg.DateAxisItem()})
            
            # 复制原有设置
            title = old_widget.titleLabel.text if hasattr(old_widget, 'titleLabel') else ""
            self.plot_widget.setTitle(title)
            self.plot_widget.setBackground('#f8f9fa')
            self.plot_widget.showGrid(x=True, y=True, alpha=0.3)
            self.plot_widget.setMouseEnabled(x=False, y=False)
            self.plot_widget.getViewBox().setMenuEnabled(False)
            
            # 禁用所有可能的交互行为
            view_box = self.plot_widget.getViewBox()
            view_box.setMouseMode(view_box.RectMode)
            
            self.plot_widget.addLegend()
            
            # 重新创建十字线和标签
            self.crosshair_v = pg.InfiniteLine(angle=90, movable=False, pen=pg.mkPen(color='gray', width=1, style=Qt.PenStyle.DashLine))
            self.crosshair_h = pg.InfiniteLine(angle=0, movable=False, pen=pg.mkPen(color='gray', width=1, style=Qt.PenStyle.DashLine))
            self.plot_widget.addItem(self.crosshair_v, ignoreBounds=True)
            self.plot_widget.addItem(self.crosshair_h, ignoreBounds=True)
            
            self.value_label = pg.TextItem(anchor=(0, 1))
            self.plot_widget.addItem(self.value_label)
            
            # 重新连接鼠标移动事件
            self.plot_widget.scene().sigMouseMoved.connect(self.mouse_moved)
            
            # 清空曲线数据
            self.plot_curves = []
            
            # 替换布局中的 widget
            layout = self.layout()
            layout.removeWidget(old_widget)
            layout.addWidget(self.plot_widget)
            old_widget.deleteLater()

    def set_x_range(self, x_min, x_max, enforce_limits=False):
        """Set X-axis range explicitly.

        If time_axis is enabled, accepts datetime/date/timestamp-like values
        and converts to epoch seconds for the underlying DateAxisItem.

        Args:
            x_min: start of x-range (datetime/date/float/int)
            x_max: end of x-range (datetime/date/float/int)
            enforce_limits: when True, also set hard limits to prevent panning
        """
        def to_ts(val):
            if self.time_axis:
                # _convert_to_timestamp accepts list; grab first converted value
                return self._convert_to_timestamp([val])[0]
            return val

        x_min_ts = to_ts(x_min)
        x_max_ts = to_ts(x_max)

        vb = self.plot_widget.getViewBox()
        if enforce_limits:
            vb.setLimits(xMin=x_min_ts, xMax=x_max_ts)
        else:
            vb.setLimits(xMin=None, xMax=None)
        # Apply exact range without padding so the axis starts exactly at input
        vb.setXRange(x_min_ts, x_max_ts, padding=0)

    def set_time_range(self, start, end, enforce_limits=False):
        """Convenience wrapper for time-based x range."""
        self.set_x_range(start, end, enforce_limits=enforce_limits)
